﻿using Nop.Core.Domain.Orders;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;

namespace Nop.Plugin.Payments.LibraPay.Models
{
	/// <summary>
	///		<para>This class provide the common methods used to exchange data with the LibraPay payment gateway.</para>
	///		<para>Fields and required steps/methods for HTTP-GET-ing and HTTP-POST-ing are documented at https://www.activare3dsecure.ro.</para>
	/// </summary>
	public class LibraPayGatewayMessage
	{

		/// <summary>
		///		<para>Computes the P-Sign hash used for "signing" the transactions with the payment gateway.</para>
		/// </summary>
		public static string P_SIGN(string EncryptionKey, string MessageToHash)
		{
			int keyLength = EncryptionKey.Length;
			int numberOfBytesInKey = (keyLength % 2 == 0) ? keyLength / 2 : keyLength / 2 + 1;
			byte[] key = new byte[numberOfBytesInKey];
			for (int i = 0; i < numberOfBytesInKey; i++)
			{
				key[i] = (byte)Convert.ToInt32(EncryptionKey.Substring(2 * i, ((2 * i + 2) > keyLength) ? 1 : 2), 16);
			}

			int messageLength = MessageToHash.Length;
			byte[] message = new byte[messageLength];
			for (int i = 0; i < messageLength; i++)
			{
				message[i] = (byte)MessageToHash[i];
			}

			HMACSHA1 hmac = new HMACSHA1(key);
			byte[] result = hmac.ComputeHash(message);
			StringBuilder stbPSign = new StringBuilder();
			foreach (byte b in result)
			{
				stbPSign.Append(string.Format("{0:X2}", b));
			}

			return stbPSign.ToString();
		}

		/// <summary>
		///		<para>Returns order items, shipping and billing info, serialized as JavaScript. This info is sent encoded in DATA_CUSTOM to the payment gateway.</para>
		/// </summary>
		/// <param name="order">The sales order holding the details</param>
		/// <param name="shippingItemName">Shipping tax (if applicable) will be added as a separate item line to order items list using this name</param>
		/// <param name="discountsItemName">Order discount (if applicable) will be added as a separate item line to order items list using this name</param>
		/// <returns></returns>
		public static string GetOrderDetailsSerializedAsJavaScript(Order order, string shippingItemName, string discountsItemName)
		{
			Dictionary<string, object> OrderDetails = new Dictionary<string, object>();
			Dictionary<string, Dictionary<string, string>> ItemsInOrder = new Dictionary<string, Dictionary<string, string>>();
			Dictionary<string, string> CustomerDetails = new Dictionary<string, string>();
			#region add order items
			Dictionary<string, string> ItemRecord;
			int productsIndex = 0;
			foreach (OrderItem orderItem in order.OrderItems)
			{
				ItemRecord = new Dictionary<string, string>();
				ItemRecord.Add("ItemName", LibraPayGatewayMessage.ChopString(orderItem.Product.Name, 50));							// VarChar(255)
				ItemRecord.Add("ItemDesc", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Categ", string.Empty);																				// VarChar(255)
				ItemRecord.Add("Subcateg", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Quantity", orderItem.Quantity.ToString());															// Int
				ItemRecord.Add("Price", orderItem.UnitPriceInclTax.ToString("#0.00", CultureInfo.InvariantCulture));				// Money
				ItemRecord.Add("ProductId", orderItem.ProductId.ToString());														// VarChar(19)
				ItemsInOrder[productsIndex.ToString()] = ItemRecord;
				productsIndex++;
			}
			if (order.OrderShippingInclTax != 0M)
			{
				ItemRecord = new Dictionary<string, string>();
				ItemRecord.Add("ItemName", LibraPayGatewayMessage.ChopString(shippingItemName, 50));								// VarChar(255)
				ItemRecord.Add("ItemDesc", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Categ", string.Empty);																				// VarChar(255)
				ItemRecord.Add("Subcateg", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Quantity", "1");																					// Int
				ItemRecord.Add("Price", order.OrderShippingInclTax.ToString("#0.00", CultureInfo.InvariantCulture));				// Money
				ItemRecord.Add("ProductId", "Delivery");																			// VarChar(19)
				ItemsInOrder[productsIndex.ToString()] = ItemRecord;
				productsIndex++;
			}
			if (order.OrderDiscount != 0M)
			{
				ItemRecord = new Dictionary<string, string>();
				ItemRecord.Add("ItemName", LibraPayGatewayMessage.ChopString(discountsItemName, 50));								// VarChar(255)
				ItemRecord.Add("ItemDesc", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Categ", string.Empty);																				// VarChar(255)
				ItemRecord.Add("Subcateg", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Quantity", "1");																					// Int
				ItemRecord.Add("Price", (-order.OrderDiscount).ToString("#0.00", CultureInfo.InvariantCulture));					// Money
				ItemRecord.Add("ProductId", "Discount");																			// VarChar(19)
				ItemsInOrder[productsIndex.ToString()] = ItemRecord;
				productsIndex++;
			}
			if (order.OrderSubTotalDiscountInclTax != 0M)
			{
				ItemRecord = new Dictionary<string, string>();
				ItemRecord.Add("ItemName", LibraPayGatewayMessage.ChopString(discountsItemName, 50));								// VarChar(255)
				ItemRecord.Add("ItemDesc", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Categ", string.Empty);																				// VarChar(255)
				ItemRecord.Add("Subcateg", string.Empty);																			// VarChar(255)
				ItemRecord.Add("Quantity", "1");																					// Int
				ItemRecord.Add("Price", (-order.OrderSubTotalDiscountInclTax).ToString("#0.00", CultureInfo.InvariantCulture));		// Money
				ItemRecord.Add("ProductId", "Discount");																			// VarChar(19)
				ItemsInOrder[productsIndex.ToString()] = ItemRecord;
				productsIndex++;
			}
			#endregion
			#region add customer details, plus shipping and billing info
			string CustomerName = order.Customer.SystemName;
			if (string.IsNullOrEmpty(CustomerName)) CustomerName = order.BillingAddress.Company;
			if (string.IsNullOrEmpty(CustomerName)) CustomerName = order.ShippingAddress.Company;
			if (string.IsNullOrEmpty(CustomerName)) CustomerName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName);
			if (string.IsNullOrEmpty(CustomerName)) CustomerName = string.Format("{0} {1}", order.ShippingAddress.FirstName, order.ShippingAddress.LastName);
			string PhoneNumber = order.BillingAddress.PhoneNumber;
			if (string.IsNullOrEmpty(PhoneNumber)) PhoneNumber = order.ShippingAddress.PhoneNumber;
			string AddressBilling1 = LibraPayGatewayMessage.ChopString(order.BillingAddress.Address1, 100);
			string AddressBilling2 = LibraPayGatewayMessage.ChopString(order.BillingAddress.Address2, 100);
			string AddressBillingProvince = order.BillingAddress.StateProvince == null ? string.Empty : order.BillingAddress.StateProvince.Name;
			string AddressShipping1 = LibraPayGatewayMessage.ChopString(order.ShippingAddress.Address1, 100);
			string AddressShipping2 = LibraPayGatewayMessage.ChopString(order.ShippingAddress.Address2, 100);
			string AddressShippingProvince = order.ShippingAddress.StateProvince == null ? string.Empty : order.ShippingAddress.StateProvince.Name;

			CustomerDetails.Add("LoginName", string.Empty);																			// VarChar(50)
			CustomerDetails.Add("Email", LibraPayGatewayMessage.ChopString(order.Customer.Email, 50));								// VarChar(50)
			CustomerDetails.Add("Name", LibraPayGatewayMessage.ChopString(CustomerName, 100));										// VarChar(255)
			CustomerDetails.Add("CNP", string.Empty);																				// VarChar(13)
			CustomerDetails.Add("Phone", LibraPayGatewayMessage.ChopString(PhoneNumber, 50));										// VarChar(50)

			CustomerDetails.Add("BillingName", LibraPayGatewayMessage.ChopString(order.BillingAddress.Company, 100));				// VarChar(100) - ->Denumire companie pentru PJ, Nume pentru PF
			CustomerDetails.Add("BillingID", LibraPayGatewayMessage.ChopString(order.VatNumber, 15));								// VarChar(15) - -> CUI pentru PJ, Serie buletin pentru PF
			CustomerDetails.Add("BillingIDNumber", string.Empty);	 																// VarChar(10)
			CustomerDetails.Add("BillingIssuedBy", string.Empty); 																	// VarChar(100)
			CustomerDetails.Add("BillingEmail", LibraPayGatewayMessage.ChopString(order.Customer.Email, 100)); 						// VarChar(100)
			CustomerDetails.Add("BillingPhone", LibraPayGatewayMessage.ChopString(order.BillingAddress.PhoneNumber, 50)); 			// VarChar(50)
			CustomerDetails.Add("BillingAddress", string.Format("{0}; \n{1}", AddressBilling1, AddressBilling2));					// VarChar(255)
			CustomerDetails.Add("BillingCity", LibraPayGatewayMessage.ChopString(order.BillingAddress.City, 50)); 					// VarChar(50)		
			CustomerDetails.Add("BillingPostalCode", LibraPayGatewayMessage.ChopString(order.BillingAddress.ZipPostalCode, 50));	// VarChar(50)	
			CustomerDetails.Add("BillingDistrict", LibraPayGatewayMessage.ChopString(AddressBillingProvince, 50));					// VarChar(50)	
			CustomerDetails.Add("BillingCountry", LibraPayGatewayMessage.ChopString(order.BillingAddress.Country.Name, 50));		// VarChar(50)

			CustomerDetails.Add("ShippingName", LibraPayGatewayMessage.ChopString(order.ShippingAddress.Company, 100)); 			// VarChar(100) - ->Denumire companie pentru PJ, Nume pentru PF
			CustomerDetails.Add("ShippingID", LibraPayGatewayMessage.ChopString(order.VatNumber, 15));								// VarChar(15) - -> CUI pentru PJ, Serie buletin pentru PF
			CustomerDetails.Add("ShippingIDNumber", string.Empty);																	// VarChar(10)
			CustomerDetails.Add("ShippingIssuedBy", string.Empty);																	// VarChar(100)
			CustomerDetails.Add("ShippingEmail", LibraPayGatewayMessage.ChopString(order.ShippingAddress.Email, 100));				// VarChar(100)
			CustomerDetails.Add("ShippingPhone", LibraPayGatewayMessage.ChopString(order.ShippingAddress.PhoneNumber, 50));			// VarChar(50)
			CustomerDetails.Add("ShippingAddress", string.Format("{0}; \n{1}", AddressShipping1, AddressShipping2)); 				// VarChar(255)
			CustomerDetails.Add("ShippingCity", LibraPayGatewayMessage.ChopString(order.ShippingAddress.City, 50));					// VarChar(50)
			CustomerDetails.Add("ShippingPostalCode", LibraPayGatewayMessage.ChopString(order.ShippingAddress.ZipPostalCode, 50));	// VarChar(50)
			CustomerDetails.Add("ShippingDistrict", LibraPayGatewayMessage.ChopString(AddressShippingProvince, 50));				// VarChar(50)
			CustomerDetails.Add("ShippingCountry", LibraPayGatewayMessage.ChopString(order.ShippingAddress.Country.Name, 50));		// VarChar(50)
			#endregion
			object[] ob1 = new object[1];
			ob1.SetValue(ItemsInOrder, 0);
			OrderDetails["ProductsData"] = ob1[0];
			object[] ob2 = new object[1];
			ob2.SetValue(CustomerDetails, 0);
			OrderDetails["UserData"] = ob2[0];
			JavaScriptSerializer serializer = new JavaScriptSerializer();
			return serializer.Serialize(OrderDetails);
		}

		/// <summary>
		///		<para>Reduces a string to maximum length</para>
		/// </summary>
		/// <param name="inputString"></param>
		/// <param name="maxLength"></param>
		/// <returns></returns>
		public static string ChopString(string inputString, int maxLength)
		{
			if (string.IsNullOrEmpty(inputString)) return string.Empty;
			if (inputString.Length <= maxLength) return inputString;
			return inputString.Substring(0, maxLength);
		}
	}

	/// <summary>
	///		<para>FORM fields used for sending an authorization (PREAUTH) request to the LibraPay payment gateway.</para>
	/// 	<para>Fields and required steps/methods for HTTP-GET-ing and HTTP-POST-ing are documented at https://www.activare3dsecure.ro.</para>
	/// 	<para>THESE FIELDS NEED TO BE SENT:</para>
	///			<para>AMOUNT		Numeric			1-12	Suma totala de plata. Trebuie sa contina 2 zecimale separate prin punct (Ex: 10.23 RON).</para>
	///			<para>CURRENCY	String			3		Moneda in care se face tranzactia. In functie de banca poate fi: RON, USD, EUR.</para>
	///			<para>ORDER		Numeric			6-19	Numarul comenzii generat de catre comerciant.</para>
	///			<para>DESC		String			1-50	Descrierea comenzii.</para>
	///			<para>MERCH_NAME	String			1-50	Numele comerciantului (numele firmei care apare pe site si este declarat la banca).</para>
	///			<para>MERCH_URL	String			1-50	Adresa site-ului.</para>
	///			<para>MERCHANT	Numeric			15		Valoare asignata de catre banca. Se compune din: 0000000+TERMINAL.</para>
	///			<para>TERMINAL	String			8		Valoare asignata de catre banca. Se gaseste in sectiunea "Configurare cont".</para>
	///			<para>EMAIL		String			1-50	Email de contact comerciant.</para>
	///			<para>TRTYPE		Numeric			1		Valoare acceptata: 0 (zero).</para>
	///			<para>COUNTRY		String			2		Va fi setat ca NULL.</para>
	///			<para>MERCH_GMT	Numeric			1-5		Va fi setat ca NULL.</para>
	///			<para>TIMESTAMP	YYYYMMDDHHMMSS	14	Va contine valoarea GMT (Ex: Valoare locala: ; Valoare GMT acceptata: ). In cazul in care diferenta intre ora trimisa si ora GMT este mai mare de 1 ora, tranzactia va fi respinsa.</para>
	///			<para>NONCE		String			32		Valoare generata aleator, unica pentru fiecare mesaj (ajuta la generarea campului de securitate P_SIGN).</para>
	///			<para>BACKREF		URL				1-80	Adresa unde va fi trimis mesajul de raspuns. Comerciantul va trebui sa primeasca (prin HTTP-GET), sa interpreteze campurile primite si sa afiseze clientului rezultatul tranzactiei.</para>
	///			<para>P_SIGN		String			40		Cod de securitate al mesajului transmis. Se va calcula pentru fiecare mesaj in parte conform indicatiilor din sectiunea"Calcul P_SIGN".</para>
	/// </summary>
	public class AuthorizeModel
	{
		public string AMOUNT { get; set; }
		public string CURRENCY { get; set; }
		public string ORDER { get; set; }
		public string DESC { get; set; }
		public string MERCH_NAME { get; set; }
		public string MERCH_URL { get; set; }
		public string MERCHANT { get; set; }
		public string TERMINAL { get; set; }
		public string EMAIL { get; set; }
		public string TRTYPE { get; set; }
		public string COUNTRY { get; set; }
		public string MERCH_GMT { get; set; }
		public string TIMESTAMP { get; set; }
		public string NONCE { get; set; }
		public string BACKREF { get; set; }
		public string DATA_CUSTOM { get; set; }
		public string P_SIGN { get; set; }

		// The following are used for showing details to shopper
		public int OrderID { get; set; }
		public decimal Amount { get; set; }
		public string Description { get; set; }
		public string PaymentGatewayURL { get; set; }
		public string OrderItems { get; set; }
		public string OrderDetailsSerializedAsJavaScript { get; set; }

		// The following are only comming with the response from payment gateway
		public string ACTION { get; set; }
		public string RC { get; set; }
		public string MESSAGE { get; set; }
		public string RRN { get; set; }
		public string INT_REF { get; set; }
		public string APPROVAL { get; set; }

		/// <summary>
		///		<para>Assuming all properties are properly filled, this gets the string message to be hashed in the P_SIGN upon Authorize request</para>
		/// </summary>
		public string MessageToHashOnAuthorizeRequest
		{
			get
			{
				if (_MessageToHash_AuthorizeRequest == null)
				{
					StringBuilder stbMessageToHash = new StringBuilder();
					stbMessageToHash.Append(this.AMOUNT.Length); stbMessageToHash.Append(this.AMOUNT);
					stbMessageToHash.Append(this.CURRENCY.Length); stbMessageToHash.Append(this.CURRENCY);
					stbMessageToHash.Append(this.ORDER.Length); stbMessageToHash.Append(this.ORDER);
					stbMessageToHash.Append(this.DESC.Length); stbMessageToHash.Append(this.DESC);
					stbMessageToHash.Append(this.MERCH_NAME.Length); stbMessageToHash.Append(this.MERCH_NAME);
					stbMessageToHash.Append(this.MERCH_URL.Length); stbMessageToHash.Append(this.MERCH_URL);
					stbMessageToHash.Append(this.MERCHANT.Length); stbMessageToHash.Append(this.MERCHANT);
					stbMessageToHash.Append(this.TERMINAL.Length); stbMessageToHash.Append(this.TERMINAL);
					stbMessageToHash.Append(this.EMAIL.Length); stbMessageToHash.Append(this.EMAIL);
					stbMessageToHash.Append(this.TRTYPE.Length); stbMessageToHash.Append(this.TRTYPE);
					if (string.IsNullOrEmpty(this.COUNTRY)) stbMessageToHash.Append("-");
					else { stbMessageToHash.Append(this.COUNTRY.Length); stbMessageToHash.Append(this.COUNTRY); }
					if (string.IsNullOrEmpty(this.MERCH_GMT)) stbMessageToHash.Append("-");
					else { stbMessageToHash.Append(this.MERCH_GMT.Length); stbMessageToHash.Append(this.MERCH_GMT); }
					stbMessageToHash.Append(this.TIMESTAMP.Length); stbMessageToHash.Append(this.TIMESTAMP);
					stbMessageToHash.Append(this.NONCE.Length); stbMessageToHash.Append(this.NONCE);
					stbMessageToHash.Append(this.BACKREF.Length); stbMessageToHash.Append(this.BACKREF);
					_MessageToHash_AuthorizeRequest = stbMessageToHash.ToString();
				}
				return _MessageToHash_AuthorizeRequest;
			}
		}

		/// <summary>
		///		<para>Builds and encodes the CUSTOM_DATA field, holding order items, shipping and billing info that will be sent to payment gateway</para>
		/// </summary>
		/// <param name="order">The sales order holding the details</param>
		/// <param name="shippingItemName">Shipping tax (if applicable) will be added as a separate item line to order items list using this name</param>
		/// <param name="discountsItemName">Order discount (if applicable) will be added as a separate item line to order items list using this name</param>
		/// <returns></returns>
		public string ComputeCustomData_AuthorizeRequest(Order order, string shippingItemName, string discountsItemName)
		{
			this.OrderDetailsSerializedAsJavaScript = LibraPayGatewayMessage.GetOrderDetailsSerializedAsJavaScript(order, shippingItemName, discountsItemName);
			byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(this.OrderDetailsSerializedAsJavaScript);
			return System.Convert.ToBase64String(toEncodeAsBytes);
		}
		public bool UseSandbox { get; set; }


		/// <summary>
		///		<para>Computes the P-Sign used when the shop sends an Authorize request to the payment gateway</para>
		/// </summary>
		/// <param name="EncryptionKey">The merchant-specific "salt" for computing the P-Sign hash</param>
		/// <returns></returns>
		public string ComputePSign_AuthorizeRequest(string EncryptionKey)
		{
			return LibraPayGatewayMessage.P_SIGN(EncryptionKey, this.MessageToHashOnAuthorizeRequest);
		}
		public string ComputePSign_AuthorizeResponse(string EncryptionKey)
		{
			StringBuilder stbMessageToHash = new StringBuilder();
			#region build message to be hashed for P_Sign
			stbMessageToHash.Append(this.TERMINAL.Length); stbMessageToHash.Append(this.TERMINAL);
			stbMessageToHash.Append(this.TRTYPE.Length); stbMessageToHash.Append(this.TRTYPE);
			stbMessageToHash.Append(this.ORDER.Length); stbMessageToHash.Append(this.ORDER);
			stbMessageToHash.Append(this.AMOUNT.Length); stbMessageToHash.Append(this.AMOUNT);
			stbMessageToHash.Append(this.CURRENCY.Length); stbMessageToHash.Append(this.CURRENCY);
			stbMessageToHash.Append(this.DESC.Length); stbMessageToHash.Append(this.DESC);
			stbMessageToHash.Append(this.ACTION.Length); stbMessageToHash.Append(this.ACTION);
			stbMessageToHash.Append(this.RC.Length); stbMessageToHash.Append(this.RC);
			stbMessageToHash.Append(this.MESSAGE.Length); stbMessageToHash.Append(this.MESSAGE);
			stbMessageToHash.Append(this.RRN.Length); stbMessageToHash.Append(this.RRN);
			stbMessageToHash.Append(this.INT_REF.Length); stbMessageToHash.Append(this.INT_REF);
			stbMessageToHash.Append(this.APPROVAL.Length); stbMessageToHash.Append(this.APPROVAL);
			stbMessageToHash.Append(this.TIMESTAMP.Length); stbMessageToHash.Append(this.TIMESTAMP);
			stbMessageToHash.Append(this.NONCE.Length); stbMessageToHash.Append(this.NONCE);
			#endregion
			return LibraPayGatewayMessage.P_SIGN(EncryptionKey, stbMessageToHash.ToString());
		}

		protected string _MessageToHash_AuthorizeRequest = null;
	}
}